// puppeteer 库会下载自带chrome，使用自带chrome启动并渲染
const helper = require('../helper');
const Lodash = require('lodash');
const {PendingXHR} = require('pending-xhr-puppeteer');
const {uploadFile} = require("../storage");

/**
 * 隐藏webdriver/headless/爬虫 特征，尽可能的像认为操作
 * 
 * @param page
 * @returns {Promise<void>}
 */
const hideWebDriverSpecific = async function (page){
    // await page.evaluateOnNewDocument(()=>{
    //     const newProto = navigator.__proto__;
    //     delete newProto.webdriver;
    //     newProto.webdriver = function (){
    //         throw new TypeError("Illegal invocation");
    //     }
    //     navigator.__proto__ = newProto;
    //     navigator.webdriver = false;
    // });
    await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36');
    await page.evaluateOnNewDocument(() => {
        Object.defineProperty(navigator, 'webdriver', {
            get: () => false,
        });
    });

    await page.evaluateOnNewDocument(() => {
        if (navigator.plugins.length === 0){
            Object.defineProperty(navigator, 'plugins', {
                get: () => [
                    {
                        0: {type: "application/x-google-chrome-pdf", suffixes: "pdf", description: "Portable Document Format", enabledPlugin: Plugin},
                        description: "Portable Document Format",
                        filename: "internal-pdf-viewer",
                        length: 1,
                        name: "Chrome PDF Plugin"
                    },
                    {
                        0: {type: "application/pdf", suffixes: "pdf", description: "", enabledPlugin: Plugin},
                        description: "",
                        filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai",
                        length: 1,
                        name: "Chrome PDF Viewer"
                    },
                    {
                        0: {type: "application/x-nacl", suffixes: "", description: "Native Client Executable", enabledPlugin: Plugin},
                        1: {type: "application/x-pnacl", suffixes: "", description: "Portable Native Client Executable", enabledPlugin: Plugin},
                        description: "",
                        filename: "internal-nacl-plugin",
                        length: 2,
                        name: "Native Client"
                    }
                ],
            });
        }
    });

    await page.evaluateOnNewDocument(() => {
        if (window.chrome === undefined){
            window.chrome =  {
                "app":{
                    "isInstalled":false,
                    "InstallState":{
                        "DISABLED":"disabled",
                        "INSTALLED":"installed",
                        "NOT_INSTALLED":"not_installed"
                    },
                    "RunningState":{
                        "CANNOT_RUN":"cannot_run",
                        "READY_TO_RUN":"ready_to_run",
                        "RUNNING":"running"
                    },
                    getDetails: function () {
                        return false;
                    }
                },
            };
        }
    });

};

const waitPageRequestComplete = async function (page) {
    helper.log("wait page all request finish");
    const pendingXHR = new PendingXHR(page);
    // Here all xhr requests are not finished
    await pendingXHR.waitForAllXhrFinished();
};

/**
 * 页面打开后，通过checkPageCompleteJs代码段在网页里的js环境执行，检查网页是否加载完整
 *
 *
 * @param page 页面对象
 * @param timeout 检查时的超时时间，超时则 reject
 * @param checkPageCompleteJs 检查页面是否加载完整的js代码，完整返回true; 如： 'window.document.readyState === "complete"'
 * @returns {Promise}
 */
const waitPageComplete = async function (page, timeout, checkPageCompleteJs) {
    if (!checkPageCompleteJs) {
        return await waitPageRequestComplete(page);
    }

    timeout = ~~timeout;
    if (timeout < 5000) {
        timeout = 5000;
    }
    if (timeout > 300000) {
        timeout = 300000;
    }
    checkPageCompleteJs = ((checkPageCompleteJs || '') + '') || 'window.document.readyState === "complete"';

    //let loadComplete = false;
    //let tickDelay = 100;
    // let time = 0;

    let checkCompleteFunc = async function () {
        let loadComplete = await page.evaluate((checkPageCompleteJs)).catch(function (e) {
            helper.error("waitPageComplete error:" + e.toString())
        });

        return loadComplete;
    };

    await helper.intervalUntil(checkCompleteFunc, 100, timeout, false).then(function () {
        helper.log("waitPageComplete complete");
    })
};

/**
 * 从页面的一个html节点，截取一张图片
 *
 * @param page
 * @param {string} selector html节点的css选择器
 * @param {boolean} saveAsFile 是否保存为文件
 * @param {string} encoding 返回数据格式化 binary或base64，默认:base64
 * @param {string} type 图片类型,jpeg或png, 默认png
 *
 * @returns {Promise<string|Buffer|void|*>}
 */
const screenshotDOMElement = async function (page, selector, type = 'png', saveAsFile= false,encoding = 'base64',) {
    type = (type === 'jpeg' || type === 'jpg') ? 'jpeg' : 'png';
    encoding = encoding === 'binary' ? 'binary' : 'base64';
    const rect = await page.evaluate(selector => {
        try {
            const element = document.querySelector(selector);
            if(element === null){
                return null;
            }
            const {left, top, width, height} = element.getBoundingClientRect();
            if (width * height !== 0) {
                return {left, top, width, height};
            } else {
                return null;
            }
        } catch (e) {
            return null;
        }
    }, selector);
    let option = {
        type: type,
        encoding: encoding,
        clip: rect ? {
            y: rect.top,
            x: rect.left,
            width: rect.width,
            height: rect.height
        } : null
    };

    let data =  Lodash.extend({},option)
    let savaInfo = null;
    if (saveAsFile) {
        savaInfo = Helper.makeScreenshotFileInfo(type)
        option.path = savaInfo.fullPath;
        // info.relatePath;
    }
    let rep = await page.screenshot(option);
    if(savaInfo){
        data.path = savaInfo.relatePath;
        await uploadFile(savaInfo.relatePath,savaInfo.fullPath);
    }
    if(encoding === 'base64'){
        data.base64 = 'data:image/'+type+';base64,' + rep;
    }else{
        data.buffer = rep;
    }

    return data;
};

/**
 * 从页面的html节点，截取多张图片
 *
 * @param page
 * @param {string[]} selectors 要截取图片的css选择器列表
 * @param {string} encoding 返回数据格式化 binary或base64，默认:base64
 * @param {string} type 图片类型,jpeg或png, 默认png
 *
 * @param saveAsFile
 * @returns {Promise<Object>}
 */
const screenshotDOMElements = async function (page, selectors, type = 'png',saveAsFile= false, encoding = 'base64') {
    type = (type === 'jpeg' || type === 'jpg') ? 'jpeg' : 'png';
    encoding = encoding === 'binary' ? 'binary' : 'base64';

    if (!Array.isArray(selectors)) {
        throw "invalid screenshot selectors";
    }

    let images = {};
    for (let i in selectors) {
        let selector = selectors[i];
        if (typeof selector !== 'string') {
            continue;
        }
        let rects = await page.evaluate(selector => {
            try {
                let elements = document.querySelectorAll(selector);
                let ranges = [];
                elements.forEach(function (element) {
                    let {left, top, width, height} = element.getBoundingClientRect();
                    if (width * height !== 0) {
                        return ranges.push({left, top, width, height});
                    }
                });

                return ranges;
            } catch (e) {
                return [];
            }
        }, selector);

        images[selector] = [];
        for (let j in rects) {
            let rect = rects[j];
            let option = {
                type: type,
                encoding: encoding,
                clip: rect ? {
                    y: rect.top,
                    x: rect.left,
                    width: rect.width,
                    height: rect.height
                } : null
            };

            let data =  Lodash.extend({},option)
            let savaInfo = null;
            if (saveAsFile) {
                savaInfo = Helper.makeScreenshotFileInfo(type)
                option.path = savaInfo.fullPath;
                // info.relatePath;
            }
            let rep = await page.screenshot(option);
            if(rep){
                if(savaInfo){
                    data.path = savaInfo.relatePath;
                    await uploadFile(savaInfo.relatePath,savaInfo.fullPath);
                }
                if(encoding === 'base64'){
                    data.base64 = 'data:image/'+type+';base64,' + rep;
                }else{
                    data.buffer = rep;
                }
                images[selector].push(data);
            }
        }
    }

    return images;
};

/**
 * 从网页生成PDF文件
 *
 * @param page
 * @param saveFile 保存pdf文件路径
 *
 * @returns {Promise<*|Buffer>}
 */
const renderPdf = async function (page, saveFile, timeout) {
    helper.log("puppeteer: start make pdf, url:" + page.url());
    let option = {
        //landscape : false,
        displayHeaderFooter: false,
        printBackground: true,
        scale: 1,
        // paperWidth : '1mm',
        // paperHeight : '1mm',
        marginTop: 0,
        marginBottom: 0,
        marginLeft: 0,
        marginRight: 0,
        // Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means print all pages.
        pageRanges: '',
        // Whether to silently ignore invalid but successfully parsed page ranges, such as '3-2'. Defaults to false.
        ignoreInvalidPageRanges: false,
        // HTML template for the print header. Should be valid HTML markup with following classes used to inject printing values into them:
        // date: formatted print date
        // title: document title
        // url: document location
        // pageNumber: current page number
        // totalPages: total pages in the document
        // For example, <span class=title></span> would generate span containing the title.
        headerTemplate: '',
        footerTemplate: '',
        // Whether or not to prefer page size as defined by css. Defaults to false, in which case the content will be scaled to fit the paper size.
        preferCSSPageSize: true,
        // Allowed Values: ReturnAsBase64, ReturnAsStream
        transferMode: 'ReturnAsStream',
        timeout: timeout
    };

    if (saveFile) {
        option.path = saveFile;
        helper.log("save pdf file:" + saveFile);
    }
    helper.log("puppeteer: make pdf start");
    let ret = await page.pdf(option);
    helper.log("puppeteer: make pdf end ");
    return ret;
};


const normalizeMetaInfo = async function (options,page) {
    let bookJsMetaInfo = {
        information: {}
    };

    if(page){
        let ret = await page.evaluate("window.bookJsMetaInfo");
        if(ret !== null && ret !== undefined){
            bookJsMetaInfo = ret;
        }
    }


    if(Lodash.isPlainObject(options.metaInfo)){
        let metaInfo = options.metaInfo;
        let information = metaInfo.information || {};

        /** old version bookjs-eazy meta options **/
        if (typeof metaInfo.Author === 'string') {
            if(metaInfo.Author){
                information.author = metaInfo.Author;
            }

            delete metaInfo.Author;
        }

        if (typeof metaInfo.Subject === 'string') {
            if(metaInfo.Subject){
                information.subject = metaInfo.Subject;
            }

            delete metaInfo.Subject;
        }
        if (typeof metaInfo.Keywords === 'string') {
            if(metaInfo.Keywords){
                information.keywords = metaInfo.Keywords;
            }

            delete metaInfo.Keywords;
        }
        delete metaInfo.title;
        metaInfo.information = information;

        bookJsMetaInfo = metaInfo;
    }

    if(Lodash.isEmpty(bookJsMetaInfo.information.subject)){
        delete bookJsMetaInfo.information.subject;
    }
    if(Lodash.isEmpty(bookJsMetaInfo.information.keywords)){
        delete bookJsMetaInfo.information.keywords;
    }
    if(Lodash.isEmpty(bookJsMetaInfo.information.author)){
        delete bookJsMetaInfo.information.author;
    }

    return bookJsMetaInfo
};


const isVisibleElement = async function(page,selector){
    return await page.evaluate((selector) => {
        const element = document.querySelector(selector);
        if (element) {
            const style = window.getComputedStyle(element);
            return style && style.display !== 'none' && style.visibility !== 'hidden';
        }
        return false;
    }, selector);
}
module.exports = {
    waitPageRequestComplete,
    waitPageComplete,
    renderPdf,
    screenshotDOMElement,
    screenshotDOMElements,
    normalizeMetaInfo,
    hideWebDriverSpecific,
    isVisibleElement,
};
